package cn.gowss.judger.core;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class MultiThreadCommandExecutorImpl implements LocalCommandExecutor {

static final Logger logger = LoggerFactory.getLogger(MultiThreadCommandExecutorImpl.class);
	

    static ExecutorService pool = new ThreadPoolExecutor(1000, 1000, 3L, TimeUnit.SECONDS,
            new ArrayBlockingQueue<Runnable>(1000));
    
	@Override
	public Map<String, Object> executeCommand(String command, String inputFilePath, String outputFilePath,
			int timeLimit, int memeryLimit) {
    	Process process = null;
    	Future<Integer> executeFuture = null;
    	MultiThreadStreamGobbler streamGobbler = null;
        try {
        	logger.info(command.toString());
			process = Runtime.getRuntime().exec(command);
			final Process p = process;
			streamGobbler = new MultiThreadStreamGobbler(process, inputFilePath, outputFilePath);
			streamGobbler.start();
			
			Callable<Integer> call = new Callable<Integer>() {
        		public Integer call() throws Exception {
        			p.waitFor();
        			return p.exitValue();
        		}
        	};
        	
        	// submit the command's call and get the result from a
        	executeFuture = pool.submit(call);
        	int exitCode = executeFuture.get(timeLimit+MultiThreadStreamGobbler.THERAD_SLEEP_TIME, TimeUnit.MILLISECONDS);
        	Map<String, Object> content = streamGobbler.getContent();
        	content.put("exitCode", Integer.valueOf(exitCode));
        	Object usedTime = content.get("usedTime");
        	if(usedTime == null) {
        		content.put("usedTime", 0);
        	}
        	Object usedMemory = content.get("usedMemory");
        	if(usedMemory == null) {
        		content.put("usedMemory", 0);
        	}
        	return content;
		} catch (IOException | InterruptedException | ExecutionException | TimeoutException e) {
			String errorMessage = "The command [" + command + "] execute failed.";
            logger.error(errorMessage, e);
            Map<String, Object> m = new HashMap<String, Object>(3);
            m.put("exitCode", Integer.valueOf(-1));
            m.put("usedTime", 0);
        	m.put("usedMemory", 0);
            return m;
		}finally {
            if (executeFuture != null) {
                try {
                    executeFuture.cancel(true);
                } catch (Exception ignore) {
                    ignore.printStackTrace();
                }
            }
            if (streamGobbler != null && !streamGobbler.isInterrupted()) {
            	streamGobbler.interrupt();
            }
            if (process != null) {
                process.destroy();
            }
        }
	}

}
